/**
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * Copyright 2011 Google Inc.
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package com.google.api.adwords.starterkit.settings;

import com.google.api.adwords.starterkit.Synchronizer.SynchronizerType;
import com.google.api.adwords.starterkit.db.structures.CampaignStructure;
import com.google.api.adwords.starterkit.db.structures.GeoTargetStructure;
import com.google.api.adwords.starterkit.db.structures.LanguageTargetStructure;
import com.google.api.adwords.starterkit.db.structures.NetworkTargetStructure;
import com.google.api.adwords.starterkit.db.structures.PlatformStructure;
import com.google.api.adwords.starterkit.utils.AdTemplate;
import com.google.api.adwords.starterkit.utils.JSONUtils;
import com.google.api.adwords.starterkit.utils.ReadWriteTextFiles;
import com.google.api.adwords.v201101.cm.AdGroupStatus;
import com.google.api.adwords.v201101.cm.BudgetBudgetDeliveryMethod;
import com.google.api.adwords.v201101.cm.BudgetBudgetPeriod;
import com.google.api.adwords.v201101.cm.CampaignStatus;
import com.google.api.adwords.v201101.cm.KeywordMatchType;
import com.google.api.adwords.v201101.cm.UserStatus;

import org.apache.log4j.ConsoleAppender;
import org.apache.log4j.FileAppender;
import org.apache.log4j.Layout;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.apache.log4j.PatternLayout;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.json.XML;

import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashSet;

/**
 * The Class StarterKitSettings loads the StarterKit settings from an XML file
 * and send it to the associated settings class.
 * 
 * @author rodrigo.f.vale@gmail.com (Rodrigo Vale)
 */
public class StarterKitSettings {
  private static final String NAME = "AdWords API Starter Kit";
  private static final String VERSION = "20110118";
  private String login;
  private String password;
  private String clientLogin;
  private String devToken;
  private boolean useSandbox = true;
  private String dbDir = "./db/";
  private String dbName = "staterkit";
  private SynchronizerType dbSyncType = SynchronizerType.STANDARD;
  private String cacheDir = "./cache/";
  private JSONObject jsonSettings;
  private long cacheExpiration = 0;
  private Date reportStart;
  private Date reportEnd;
  private static final Logger log = Logger.getLogger("StarterKit");
  private boolean downloadDeletedCampaigns = false;

  int retriesOnError = 3;
  int sleepOnInternalError = 60000;
  int sleepOnPolicyError = 30000;

  CampaignSettings campaignSettings;
  AdGroupSettings adGroupSettings;
  KeywordSettings keywordSettings;
  TextAdSettings textAdSettings;

  /**
   * Instantiates a new starter kit settings with default values
   * 
   */
  public StarterKitSettings() {
    log.setAdditivity(false);
    final Layout pl = new PatternLayout("%5p [%d{ISO8601}] %m%n");
    log.addAppender(new ConsoleAppender(pl));

    GregorianCalendar startCalendar = new GregorianCalendar();
    startCalendar.add(Calendar.DAY_OF_MONTH, -2);
    reportStart = startCalendar.getTime();

    GregorianCalendar endCalendar = new GregorianCalendar();
    endCalendar.add(Calendar.DAY_OF_MONTH, -32);
    reportEnd = endCalendar.getTime();

    campaignSettings = new CampaignSettings();
    adGroupSettings = new AdGroupSettings();
    keywordSettings = new KeywordSettings();
    textAdSettings = new TextAdSettings();
  }
  
  private void processCampaignsSettings(JSONObject jsonSettings) 
      throws JSONException {
    JSONObject jsonCampaign = new JSONObject();
    campaignSettings = new CampaignSettings();

    if (hasKey(jsonSettings, "Campaign", "No Campaign "
        + "defined in Settings. Using default values.")) {
      jsonCampaign = jsonSettings.getJSONObject("Campaign");
    }

    if (hasKey(jsonCampaign, "DefaultCampaignStatus", "No CampaignStatus "
        + "defined. Using default value: " + campaignSettings.getStatus())) {
      String status = jsonCampaign.getJSONObject("DefaultCampaignStatus")
              .getString("value").toUpperCase();
      campaignSettings.setStatus(status);
    }

    if (hasKey(jsonCampaign, "DefaultBudgetAmount",
        "No BudgetAmount " + "defined. Using default value: "
        + campaignSettings.getBudgetAmount())) {
      long defaultBudgetAmount =
          jsonCampaign.getJSONObject("DefaultBudgetAmount").getLong("value");
      campaignSettings.setBudget(defaultBudgetAmount);
    }

    if (hasKey(jsonCampaign, "EndDate", "No EndDate "
        + "defined. Using default value: " + campaignSettings.getEndDay())) {
      String defaultEndDay =
          jsonCampaign.getJSONObject("EndDate").getString("value");
      campaignSettings.setEndDay(defaultEndDay);
    }

    if (hasKey(jsonCampaign, "DefaultBudgetPeriod",
        "No BudgetPeriod " + "defined. Using default value: "
        + campaignSettings.getBudgetPeriod())) {
      String budgetPeriod =
          jsonCampaign.getJSONObject("DefaultBudgetPeriod")
              .getString("value").toUpperCase();
      campaignSettings.setBudgetPeriod(budgetPeriod);
    }

    if (hasKey(jsonCampaign, "DefaultDeliveryMethod",
        "No DeliveryMethod " + "defined. Using default value: "
        + campaignSettings.getDeliveryMethod())) {
      String deliveryMethod =
          jsonCampaign.getJSONObject("DefaultDeliveryMethod")
              .getString("value").toUpperCase();
      campaignSettings.setDeliveryMethod(deliveryMethod);
    }

    if (hasKey(jsonCampaign, "AutoPause", "No AutoPause for campaign "
        + "defined. Using default value: " 
        + campaignSettings.getAutoPause())) {
      boolean autoPause =
          JSONUtils.getJSONBoolean(jsonCampaign.getJSONObject("AutoPause"),
              "value", true);
      campaignSettings.setAutoPause(autoPause);
    }

    if (hasKey(jsonCampaign, "AutoCreation",
        "No AutoCreation for campaign " + "defined. Using default value: "
        + campaignSettings.getAutoCreation())) {
      boolean autoCreation =
          JSONUtils.getJSONBoolean(jsonCampaign.getJSONObject("AutoCreation"),
              "value", true);
      campaignSettings.setAutoCreation(autoCreation);
    }

    if (hasKey(jsonCampaign, "SyncStatus", "No SyncStatus for campaign "
        + "defined. Using default value: " 
        + campaignSettings.getCanModifyStatus())) {
      boolean syncStatus =
          JSONUtils.getJSONBoolean(jsonCampaign.getJSONObject("SyncStatus"),
              "value", true);
      campaignSettings.setCanModifyStatus(syncStatus);
    }

    if (hasKey(jsonCampaign, "SyncBudget", "No SyncBudget for campaign "
        + "defined. Using default value: " 
        + campaignSettings.getCanModifyBudget())) {
      boolean syncBudget =
          JSONUtils.getJSONBoolean(jsonCampaign.getJSONObject("SyncBudget"),
              "value", true);
      campaignSettings.setCanModifyBudget(syncBudget);
    }

    if (hasKey(jsonCampaign, "BiddingStrategy",
        "No BiddingStrategy for " + "campaign defined. Using default value: "
        + campaignSettings.getBiddingStrategy())) {
      JSONObject jsonBiddingStrategy =
          jsonCampaign.getJSONObject("BiddingStrategy");
      String biddingStrategy =
          JSONUtils.getJSONString(jsonBiddingStrategy, "value", "ManualCPC")
            .toUpperCase();
      
      CampaignStructure.validateBiddingStrategy(biddingStrategy);
      campaignSettings.setBiddingStrategy(biddingStrategy);

      if (hasKey(jsonBiddingStrategy, "PricingModel",
          "No PricingModel for " + "campaign defined. Using default value: "
          + campaignSettings.getPricingModel())) {
        JSONObject jsonPricingModel =
            jsonBiddingStrategy.getJSONObject("PricingModel");
        String pricingModel =
            JSONUtils.getJSONString(jsonPricingModel, "value", "")
              .toUpperCase();
        CampaignStructure.validatePricingModel(pricingModel);
        campaignSettings.setPricingModel(pricingModel);
      }

      if (hasKey(jsonBiddingStrategy, "BidCeiling",
          "No BidCeiling for " + "campaign defined. Using default value: "
          + campaignSettings.getBidCeiling())) {
        JSONObject jsonBidCeiling =
            JSONUtils.getJSONObject(jsonBiddingStrategy, "BidCeiling");
        long bidCeiling = JSONUtils.getJSONLong(jsonBidCeiling, "value", 0);
        campaignSettings.setBidCeiling(bidCeiling);
      }
    }

    if (hasKey(jsonCampaign, "Targets", "No Targets for "
        + "campaign defined. Using default value.")) {
      JSONObject jsonTargets = jsonCampaign.getJSONObject("Targets");

      if (hasKey(jsonTargets, "LanguageTargets", "No LanguageTargets for "
          + "campaign defined. Using default value.")) {
        HashSet<LanguageTargetStructure> languageTargetList =
            new HashSet<LanguageTargetStructure>();
        JSONArray languageTarget =
            JSONUtils.getJSONArray(jsonTargets, "LanguageTargets",
                "LanguageTarget");
        for (int i = 0; i < languageTarget.length(); i++) {
          LanguageTargetStructure languageTargetStructure =
              new LanguageTargetStructure(0, 0, 
                  languageTarget.getJSONObject(i).getString("value"));
          languageTargetList.add(languageTargetStructure);
        }
        campaignSettings.setLanguageTarget(languageTargetList);
      }

      if (hasKey(jsonTargets, "PlatformTargets", "No PlatformTargets for "
          + "campaign defined. Using default value.")) {
        HashSet<String> platformTargetList = new HashSet<String>();
        JSONArray platformTarget =
            JSONUtils.getJSONArray(jsonTargets, "PlatformTargets",
                "PlatformTarget");
        for (int i = 0; i < platformTarget.length(); i++) {
          String platform = platformTarget.getJSONObject(i).getString("value");
          platformTargetList.add(platform);
        }
        campaignSettings.setPlatformTarget(platformTargetList);
      }

      if (hasKey(jsonTargets, "NetworkTargets", "No NetworkTargets for "
          + "campaign defined. Using default value.")) {
        HashSet<NetworkTargetStructure> networkTargetList 
            = new HashSet<NetworkTargetStructure>();
        JSONArray netTarget =
            JSONUtils.getJSONArray(jsonTargets, "NetworkTargets",
                "NetworkTarget");
        for (int i = 0; i < netTarget.length(); i++) {
          NetworkTargetStructure networkTargetStructure 
              = new NetworkTargetStructure();
          String network = netTarget.getJSONObject(i).getString("value");
          networkTargetStructure.setNetwork(network);          
          networkTargetList.add(networkTargetStructure);
        }
        campaignSettings.setNetworkTarget(networkTargetList);
      }

      if (hasKey(jsonTargets, "GeoTargets", "No GeoTargets for "
          + "campaign defined. Using default value.")) {
        HashSet<GeoTargetStructure> geoTargetList =
            new HashSet<GeoTargetStructure>();
        JSONArray geoTargets =
            JSONUtils.getJSONArray(jsonTargets, "GeoTargets", "GeoTarget");
        for (int i = 0; i < geoTargets.length(); i++) {
          JSONObject geoTarget = geoTargets.getJSONObject(i);          
          GeoTargetStructure geoTargetSettings = new GeoTargetStructure(
              0, 
              0,
              JSONUtils.getJSONString(geoTarget, "cityCode", ""), 
              JSONUtils.getJSONString(geoTarget, "provinceCode", ""), 
              JSONUtils.getJSONString(geoTarget, "countryCode", ""), 
              JSONUtils.getJSONString(geoTarget, "exclude", "false")
              .equals("true"));
          geoTargetList.add(geoTargetSettings);
        }
        campaignSettings.setGeoTarget(geoTargetList);
      }
    }    
  }
  
  private void processTextAdsSettings(JSONObject jsonSettings) 
      throws JSONException {
    textAdSettings = new TextAdSettings();
    JSONObject jsonCreative = new JSONObject();
    if (hasKey(jsonSettings, "Creatives", "No Creatives "
        + "defined in Settings. Using default values.")) {
      jsonCreative = jsonSettings.getJSONObject("Creatives");
    }

    if (hasKey(jsonCreative, "AutoPause",
        "No AutoPause " + "defined for Creatives. Using default value: "
            + textAdSettings.getAutoPause())) {
      boolean autoPause =
          JSONUtils.getJSONBoolean(jsonCreative.getJSONObject("AutoPause"),
              "value", true);
      textAdSettings.setAutoPause(autoPause);
    }

    if (hasKey(jsonCreative, "SyncStatus",
        "No SyncStatus " + "defined for Creatives. Using default value: "
            + textAdSettings.getSyncStatus())) {
      boolean syncStatus =
          JSONUtils.getJSONBoolean(jsonCreative.getJSONObject("SyncStatus"),
              "value", true);
      textAdSettings.setSyncStatus(syncStatus);
    }

    if (hasKey(jsonCreative, "DisplayUrl",
        "No DisplayUrl " + "defined for Creatives. Using default value: "
            + textAdSettings.getDisplayUrl())) {
      String displayUrl =
          JSONUtils.getJSONString(jsonCreative.getJSONObject("DisplayUrl"),
              "value", "");
      textAdSettings.setDisplayUrl(displayUrl);
    }

    if (hasKey(jsonCreative, "DestUrl",
        "No DestUrl " + "defined for Creatives. Using default value: "
            + textAdSettings.getDestUrl())) {
      String destUrl =
          JSONUtils.getJSONString(jsonCreative.getJSONObject("DestUrl"),
              "value", "");
      textAdSettings.setDestUrl(destUrl);
    }

    if (hasKey(jsonCreative, "AdTemplates", "No AdTemplates "
        + "defined for Creatives. Using default value")) {
      HashSet<AdTemplate> adTemplates = new HashSet<AdTemplate>();
      JSONArray jsonTemplates =
          JSONUtils.getJSONArray(jsonCreative, "AdTemplates", "Template");
      for (int i = 0; i < jsonTemplates.length(); i++) {
        JSONObject jsonTemplate = jsonTemplates.getJSONObject(i);
        JSONObject jsonHead = jsonTemplate.getJSONObject("Head");
        String head = JSONUtils.getJSONString(jsonHead, "value", "");

        JSONObject jsonLine1 = jsonTemplate.getJSONObject("Line1");
        String line1 = JSONUtils.getJSONString(jsonLine1, "value", "");

        JSONObject jsonLine2 = jsonTemplate.getJSONObject("Line2");
        String line2 = JSONUtils.getJSONString(jsonLine2, "value", "");

        JSONArray jsonTags =
            JSONUtils.getJSONArray(jsonTemplate, "Tags", "Tag");
        HashSet<String> tags = new HashSet<String>();
        for (int ii = 0; ii < jsonTags.length(); ii++) {
          JSONObject jsonTag = jsonTags.getJSONObject(ii);
          String tag = JSONUtils.getJSONString(jsonTag, "value", "");
          tags.add(tag);
        }
        AdTemplate adTemplate =
            new AdTemplate(head, line1, line2, tags,
                textAdSettings.getDisplayUrl(), textAdSettings.getDestUrl());
        adTemplates.add(adTemplate);
      }
      textAdSettings.setAdTemplates(adTemplates);
    }  
  }
  
  private void processAdGroupsSettings(JSONObject jsonSettings) 
      throws JSONException {
    // ADGROUP SETTINGS
    JSONObject jsonAdGroup = new JSONObject();
    adGroupSettings = new AdGroupSettings();

    if (hasKey(jsonSettings, "AdGroup", "No AdGroup "
        + "defined in Settings. Using default values")) {
      jsonAdGroup = jsonSettings.getJSONObject("AdGroup");
    }

    if (hasKey(jsonAdGroup, "AutoPause",
        "No AutoPause " + "defined for AdGroup. Using default value: "
            + adGroupSettings.getAutoPause())) {
      boolean autoPause =
          JSONUtils.getJSONBoolean(jsonAdGroup.getJSONObject("AutoPause"),
              "value", true);
      adGroupSettings.setAutoPause(autoPause);
    }

    if (hasKey(jsonAdGroup, "AutoCreation",
        "No AutoCreation " + "defined for AdGroup. Using default value: "
            + adGroupSettings.getAutoCreation())) {
      boolean autoCreation =
          JSONUtils.getJSONBoolean(jsonAdGroup.getJSONObject("AutoCreation"),
              "value", true);
      adGroupSettings.setAutoCreation(autoCreation);
    }

    if (hasKey(jsonAdGroup, "SyncBid",
        "No SyncBid " + "defined for AdGroup. Using default value: "
            + adGroupSettings.isBidModifiable())) {
      boolean syncBid =
          JSONUtils.getJSONBoolean(jsonAdGroup.getJSONObject("SyncBid"),
              "value", true);
      adGroupSettings.setBidModifiable(syncBid);
    }

    if (hasKey(jsonAdGroup, "SyncStatus",
        "No SyncStatus " + "defined for AdGroup. Using default value: "
            + adGroupSettings.isStatusModifiable())) {
      boolean syncStatus =
          JSONUtils.getJSONBoolean(jsonAdGroup.getJSONObject("SyncStatus"),
              "value", true);
      adGroupSettings.setStatusModifiable(syncStatus);
    }

    if (hasKey(jsonAdGroup, "DefaultMaxCpc",
        "No MaxCpc " + "defined for AdGroup. Using default value: "
            + adGroupSettings.getCpc())) {
      long adGroupDefaultCpc =
          JSONUtils.getJSONLong(jsonAdGroup.getJSONObject("DefaultMaxCpc"),
              "value", 0);
      adGroupSettings.setCpc(adGroupDefaultCpc);
    }

    if (hasKey(jsonAdGroup, "DefaultMaxCpm",
        "No MaxCpm " + "defined for AdGroup. Using default value: "
            + adGroupSettings.getCpm())) {
      long adGroupDefaultCpm =
          JSONUtils.getJSONLong(jsonAdGroup.getJSONObject("DefaultMaxCpm"),
              "value", 0);
      adGroupSettings.setCpm(adGroupDefaultCpm);
    }

    if (hasKey(jsonAdGroup, "DefaultMaxCpa",
        "No MaxCpa " + "defined for AdGroup. Using default value: "
            + adGroupSettings.getCpa())) {
      long adGroupDefaultCpa =
          JSONUtils.getJSONLong(jsonAdGroup.getJSONObject("DefaultMaxCpa"),
              "value", 0);
      adGroupSettings.setCpa(adGroupDefaultCpa);
    }

    if (hasKey(jsonAdGroup, "DefaultStatus",
        "No Status " + "defined for AdGroup. Using default value: "
            + adGroupSettings.getStatus())) {
      String status = JSONUtils.getJSONString(jsonAdGroup.getJSONObject("DefaultStatus"),
              "value", "paused").toUpperCase();
      adGroupSettings.setStatus(status);
    }    
  }
  
  private void processAdWordsAPISettings(JSONObject jsonSettings) 
      throws JSONException {
    if (jsonSettings.has("AdWordsAPI")) {
      JSONObject jsonAdWordsAPI = jsonSettings.getJSONObject("AdWordsAPI");

      if (jsonAdWordsAPI.has("Login")) {
        login = jsonAdWordsAPI.getJSONObject("Login").getString("value");
        if (login.equals("INSERT_LOGIN_EMAIL_HERE")) {
          log.error("You can't use the default login value in AdWordsAPI. "
              + "create your own AdWords API account and define values in "
              + "the settings.xml file.");
          System.exit(1);
        }

        password =
            jsonAdWordsAPI.getJSONObject("Password").getString("value");
        if (password.equals("INSERT_PASSWORD_HERE")) {
          log.error("You can't use the default password value in "
              + "AdWordsAPI. Create your own AdWords API account and define "
              + "values in the settings.xml file.");
          System.exit(1);
        }

        devToken =
            jsonAdWordsAPI.getJSONObject("DevToken").getString("value");
        if (devToken.equals("INSERT_DEVELOPER_TOKEN_HERE")) {
          log.error("You can't use the default devToken value in "
              + "AdWordsAPI. Create your own AdWords API account and define "
              + "values in the settings.xml file.");
          System.exit(1);
        }

        clientLogin =
            jsonAdWordsAPI.getJSONObject("ClientLogin").getString("value");
        if (clientLogin.equals("INSERT_CLIENT_LOGIN_EMAIL_HERE")) {
          log.error("You can't use the default clientLogin value in "
              + "AdWordsAPI. If you don't want to use a MCC account or"
              + "don't knows what does this mean, leave this field blank.");
          System.exit(1);
        }

        if (hasKey(jsonAdWordsAPI, "UseSandbox", "No UseSandbox "
            + "defined in Settings. Using default value: " + useSandbox)) {
          JSONObject jsonUseSandbox =
              JSONUtils.getJSONObject(jsonAdWordsAPI, "UseSandbox");
          useSandbox =
              JSONUtils.getJSONBoolean(jsonUseSandbox, "value", false);
        }

        if ((useSandbox) && (!devToken.matches(".+\\@.+\\+\\+\\w+"))) {
          log.error("You defined to use a sandbox account in the settings "
              + "file. But seems that you are using a wrong sandBox "
              + "devToken. Check your settings file and fix the AdWordsAPI "
              + "tags values");
          System.exit(1);
        }

        if (hasKey(jsonAdWordsAPI, "RetriesOnError", "No RetriesOnError "
            + "defined in Settings. Using default value: " 
            + retriesOnError)) {
          retriesOnError =
              jsonAdWordsAPI.getJSONObject("RetriesOnError").getInt("value");
        }

        if (hasKey(jsonAdWordsAPI, "SleepOnInternalError",
            "No SleepOnInternalError defined in Settings. Using default "
            + "value: " + sleepOnInternalError)) {
          sleepOnInternalError =
              jsonAdWordsAPI.getJSONObject("SleepOnInternalError")
                  .getInt("value");
        }

        if (hasKey(jsonAdWordsAPI, "sleepOnPolicyError",
            "No sleepOnPolicyError defined in Settings. Using default "
                + "value: " + sleepOnPolicyError)) {
          sleepOnPolicyError =
              jsonAdWordsAPI.getJSONObject("SleepOnPolicyError")
                  .getInt("value");
        }
      } else {
        log.error("I can't move ahead without an AdWords API account set "
            + "property. Define a correct AdWordsAPI tag in the " 
            + "settings file");
        System.exit(1);
      }
    } else {
      log.error("I can't move ahead without an AdWords API account set "
          + "property. Define a correct AdWordsAPI tag in the settings file");
      System.exit(1);
    }    
  }
  
  private void processDataBaseSettings(JSONObject jsonSettings) 
      throws JSONException {
    JSONObject jsonDatabase = new JSONObject();
    if (hasKey(jsonSettings, "Database", "No Database "
        + "defined in Settings. Using default values.")) {
      jsonDatabase = jsonSettings.getJSONObject("Database");

      if (hasKey(jsonDatabase, "Dir", "No dbDir "
          + "defined in Settings. Using default values: " + dbDir)) {
        dbDir = jsonDatabase.getJSONObject("Dir").getString("value");
      }

      if (hasKey(jsonDatabase, "SyncType", "No SyncType "
          + "defined in Settings. Using default values: " + dbSyncType)) {
        String syncType =
            jsonDatabase.getJSONObject("SyncType").getString("value");
        if (syncType.equals("SEVERE")) {
          dbSyncType = SynchronizerType.DEEP;
        } else if (syncType.equals("STANDARD")) {
          dbSyncType = SynchronizerType.STANDARD;
        } else if (syncType.equals("NO")) {
          dbSyncType = SynchronizerType.NO;
        } else if (syncType.equals("TEST")) {
          dbSyncType = SynchronizerType.TEST;
        }
      }
    }
  }
  
  private void processReportSettings(JSONObject jsonSettings) 
      throws JSONException {
    JSONObject jsonReport = new JSONObject();
    if (hasKey(jsonSettings, "Report", "No Report "
        + "defined in Settings. Using default values.")) {
      jsonReport = jsonSettings.getJSONObject("Report");
    }

    int start = -30;
    int end = -2;
    if (jsonReport.has("DataRange")) {
      JSONObject jsonDataRange = jsonReport.getJSONObject("DataRange");

      if (jsonDataRange.has("start")) {
        start = jsonReport.getJSONObject("DataRange").getInt("start");
      } else {
        log.debug("no start day defined in DataRange. Using default value:"
            + start);
      }

      if (jsonDataRange.has("end")) {
        end = jsonReport.getJSONObject("DataRange").getInt("end");
      } else {
        log.debug("no end day defined in DataRange. Using default value:"
            + end);
      }
    } else {
      log.debug("No DataRange. Using default value");
    }
    GregorianCalendar startCalendar = new GregorianCalendar();
    startCalendar.add(Calendar.DAY_OF_MONTH, start);
    reportStart = startCalendar.getTime();

    GregorianCalendar endCalendar = new GregorianCalendar();
    endCalendar.add(Calendar.DAY_OF_MONTH, end);
    reportEnd = endCalendar.getTime();

    if (jsonReport.has("DownloadDeletedCampaigns")
        && jsonReport.getJSONObject("DownloadDeletedCampaigns")
            .has("value")) {
      downloadDeletedCampaigns =
          jsonReport.getJSONObject("DownloadDeletedCampaigns")
              .getBoolean("value");
    } else {
      log.debug("No DownloadDeletedCampaigns. Using default value:"
          + downloadDeletedCampaigns);
    }    
  }
  
  private void processLogSettings(JSONObject jsonSettings) 
      throws JSONException {
    log.setAdditivity(false);
    final Layout pl = new PatternLayout("%5p [%d{ISO8601}] %m%n");
    
    if (jsonSettings.has("Log")) {
      JSONObject jsonLog = jsonSettings.getJSONObject("Log");
      if (jsonLog.has("LogLevel")) {
        JSONObject jsonLevel = jsonLog.getJSONObject("LogLevel");
        String logLevel = jsonLevel.getString("value");
        if (logLevel.equals("DEBUG")) {
          log.setLevel(Level.DEBUG);
        } else if (logLevel.equals("ERROR")) {
          log.setLevel(Level.ERROR);
        } else if (logLevel.equals("ALL")) {
          log.setLevel(Level.ALL);
        } else if (logLevel.equals("FATAL")) {
          log.setLevel(Level.FATAL);
        } else if (logLevel.equals("INFO")) {
          log.setLevel(Level.INFO);
        } else if (logLevel.equals("OFF")) {
          log.setLevel(Level.OFF);
        }
      }
      if (jsonLog.has("LogOutput")) {
        JSONObject jsonOutput = jsonLog.getJSONObject("LogOutput");
        String logOutput = jsonOutput.getString("value");
        if (logOutput.equals("file")) {
          try {
            log.addAppender(new FileAppender(pl, "./logs/starterkit.log"));
          } catch (IOException e) {
            e.printStackTrace();
          }
        } else if (logOutput.equals("console")) {
          log.addAppender(new ConsoleAppender(pl));
        }
      }
    }    
  }
  
  private void processKeywordsSettings(JSONObject jsonSettings) 
      throws JSONException {
    JSONObject jsonKeyword = new JSONObject();
    keywordSettings = new KeywordSettings();

    if (hasKey(jsonSettings, "Keyword", "No Keyword "
        + "defined in Settings. Using default values")) {
      jsonKeyword = jsonSettings.getJSONObject("Keyword");
    }

    if (hasKey(jsonKeyword, "AutoPause",
        "No AutoPause " + "defined for Keywords. Using default value: "
            + keywordSettings.getAutoPause())) {
      boolean autoPause =
          JSONUtils.getJSONBoolean(jsonKeyword.getJSONObject("AutoPause"),
              "value", true);
      keywordSettings.setAutoPause(autoPause);
    }

    if (hasKey(jsonKeyword, "AutoCreation",
        "No AutoCreation " + "defined for Keywords. Using default value: "
            + keywordSettings.getAutoCreation())) {
      boolean autoCreation =
          JSONUtils.getJSONBoolean(jsonKeyword.getJSONObject("AutoCreation"),
              "value", true);
      keywordSettings.setAutoCreation(autoCreation);
    }

    if (hasKey(jsonKeyword, "DefaultMaxCpc",
        "No MaxCpc " + "defined for Keywords. Using default value: "
            + keywordSettings.getCpc())) {
      long keywordDefaultCpc =
          JSONUtils.getJSONLong(jsonKeyword.getJSONObject("DefaultMaxCpc"),
              "value", 0);
      keywordSettings.setCpc(keywordDefaultCpc);
    }

    if (hasKey(jsonKeyword, "DefaultMaxCpm",
        "No MaxCpm " + "defined for Keywords. Using default value: "
            + keywordSettings.getCpm())) {
      long keywordDefaultCpm =
          JSONUtils.getJSONLong(jsonKeyword.getJSONObject("DefaultMaxCpm"),
              "value", 0);
      keywordSettings.setCpm(keywordDefaultCpm);
    }

    if (hasKey(jsonKeyword, "DefaultStatus",
        "No Status " + "defined for Keywords. Using default value: "
            + keywordSettings.getStatus())) {
      String status = JSONUtils
          .getJSONString(jsonKeyword.getJSONObject("DefaultStatus"),
              "value", "paused").toUpperCase();
      keywordSettings
          .setStatus(status);
    }

    if (hasKey(jsonKeyword, "MatchType",
        "No MatchType " + "defined for Keywords. Using default value: "
            + keywordSettings.getMatchType())) {
      String matchType = 
          JSONUtils.getJSONString(jsonKeyword.getJSONObject("MatchType"),
              "value", "broad").toUpperCase();
      keywordSettings.setMatchType(matchType);
    }

    if (hasKey(jsonKeyword, "SyncBid",
        "No SyncBid " + "defined for Keywords. Using default value: "
            + keywordSettings.getCanModifyBid())) {
      boolean syncBid =
          JSONUtils.getJSONBoolean(jsonKeyword.getJSONObject("SyncBid"),
              "value", true);
      keywordSettings.setCanModifyBid(syncBid);
    }

    if (hasKey(jsonKeyword, "SyncStatus",
        "No SyncStatus " + "defined for Keywords. Using default value: "
            + keywordSettings.getCanModifyStatus())) {
      boolean syncStatus =
          JSONUtils.getJSONBoolean(jsonKeyword.getJSONObject("SyncStatus"),
              "value", true);
      keywordSettings.setCanModifyStatus(syncStatus);
    }

    if (hasKey(jsonKeyword, "SyncUrl",
        "No SyncUrl " + "defined for Keywords. Using default value: "
            + keywordSettings.getCanModifyUrl())) {
      boolean syncUrl =
          JSONUtils.getJSONBoolean(jsonKeyword.getJSONObject("SyncUrl"),
              "value", true);
      keywordSettings.setCanModifyUrl(syncUrl);
    }    
  }

  /**
   * Instantiates a new starter kit settings and loads the settings from the
   * settings file.
   * 
   * @param settingsFile the settings file
   */
  public StarterKitSettings(String settingsFile) {
    String xmlSettings = ReadWriteTextFiles.getContents(settingsFile);

    try {
      jsonSettings = XML.toJSONObject(xmlSettings).getJSONObject("Settings");
      
      processLogSettings(jsonSettings);
      processDataBaseSettings(jsonSettings);
      processReportSettings(jsonSettings);
      processAdWordsAPISettings(jsonSettings);
      processCampaignsSettings(jsonSettings);
      processAdGroupsSettings(jsonSettings);
      processKeywordsSettings(jsonSettings);
      processTextAdsSettings(jsonSettings);

      (new File(dbDir)).mkdirs();
      (new File(cacheDir)).mkdirs();
    } catch (JSONException e) {
      System.err.print("Failed to load settings at " + settingsFile);
      System.exit(1);
    }
  }

  /**
   * Gets the JSON representation of the settings.xml file
   * 
   * @return start date
   */
  public JSONObject getJSONSettings() {
    return jsonSettings;
  }

  /**
   * Gets the start date of the AdWords performance report.
   * 
   * @return report start date
   */
  public String getReportStart() {
    SimpleDateFormat dateformat = new SimpleDateFormat("yyyyMMdd");
    return dateformat.format(reportStart);
  }

  /**
   * Gets the end date of the AdWords performance report.
   * 
   * @return report end date
   */
  public String getReportEnd() {
    SimpleDateFormat dateformat = new SimpleDateFormat("yyyyMMdd");
    return dateformat.format(reportEnd);
  }

  /**
   * Gets the cache dir value.
   * 
   * @return the cache dir
   */
  public String getCacheDir() {
    return cacheDir;
  }

  /**
   * Gets the cache expiration.
   * 
   * @return the cache expiration
   */
  public long getCacheExpiration() {
    return cacheExpiration;
  }

  /**
   * Gets the db sync type.
   * 
   * @return the db sync type
   */
  public SynchronizerType getDbSyncType() {
    return dbSyncType;
  }

  /**
   * Gets the db dir.
   * 
   * @return the db dir
   */
  public String getDbDir() {
    return dbDir;
  }

  /**
   * Returns name of this framework.
   * 
   * @return framework's name
   */
  public String getName() {
    return NAME;
  }

  /**
   * Returns version of this framework.
   * 
   * @return framework's version
   */
  public static String getVersion() {
    return VERSION;
  }

  /**
   * Returns whether to use sandbox.
   * 
   * @return true if sandbox is to be used, false otherwise
   */
  public boolean doesUseSandbox() {
    return useSandbox;
  }

  /**
   * Returns the AdWords account's login email.
   * 
   * @return AdWords account's login email
   */
  public String getLogin() {
    return login;
  }

  /**
   * Returns the AdWords account's password.
   * 
   * @return AdWords account's password
   */
  public String getPassword() {
    return password;
  }

  /**
   * Returns the client AdWords account's login email.
   * 
   * @return client AdWords account's login email
   */
  public String getClientLogin() {
    return clientLogin;
  }

  /**
   * Returns the developer token.
   * 
   * @return developer token
   */
  public String getDevToken() {
    return devToken;
  }

  /**
   * Sets the login.
   * 
   * @param login the new login
   */
  public void setLogin(String login) {
    this.login = login;
  }

  /**
   * Sets the password.
   * 
   * @param password the new password
   */
  public void setPassword(String password) {
    this.password = password;
  }

  /**
   * Sets the client login.
   * 
   * @param clientLogin the new client login
   */
  public void setClientLogin(String clientLogin) {
    this.clientLogin = clientLogin;
  }

  /**
   * Sets the dev token.
   * 
   * @param devToken the new dev token
   */
  public void setDevToken(String devToken) {
    this.devToken = devToken;
  }

  public CampaignSettings getCampaignSettings() {
    return campaignSettings;
  }

  public AdGroupSettings getAdGroupSettings() {
    return adGroupSettings;
  }

  public TextAdSettings getTextAdSettings() {
    return textAdSettings;
  }

  public KeywordSettings getKeywordSettings() {
    return keywordSettings;
  }

  /**
   * @return the retriesOnError setting
   */
  public int getRetriesOnError() {
    return retriesOnError;
  }

  /**
   * @return the sleepOnInternalError setting
   */
  public int getSleepOnInternalError() {
    return sleepOnInternalError;
  }

  /**
   * @return the sleepOnPolicyError setting
   */
  public int getSleepOnPolicyError() {
    return sleepOnPolicyError;
  }

  /**
   * Sets the location of the DB
   * 
   * @param dir the directory of the database
   */
  public void setDBDir(String dir) {
    this.dbDir = dir;
    (new File(dbDir)).mkdirs();
  }

  /**
   * Sets the sandbox flag
   * 
   * @param sandbox the sandbox flag
   */
  public void setSandBox(boolean sandbox) {
    this.useSandbox = sandbox;
  }

  /**
   * Sets the syncronization method to download an AdWords account
   * 
   * @param type the sync type
   */
  public void setSyncType(SynchronizerType type) {
    this.dbSyncType = type;
  }

  /**
   * Sets the cache directory
   * 
   * @param cacheDir the cache dir
   */
  public void setCacheDir(String cacheDir) {
    this.cacheDir = cacheDir;
  }

  /**
   * Sets start date for Adwords Reports
   * 
   * @param reportStart The start date
   */
  public void setReportStartDate(Date reportStart) {
    this.reportStart = reportStart;
  }

  /**
   * Sets end date for Adwords Reports
   * 
   * @param reportEnd The end date
   */
  public void setReportEndDate(Date reportEnd) {
    this.reportEnd = reportEnd;
  }

  /**
   * Gets the status of the DownloadDeletedCampaigns flag in the StarterKit
   * settings.
   * 
   * @return true if starterkit will download all campaigns (including the
   *         deleted ones) - may be very time consuming if true, for very old
   *         accounts.
   */
  public boolean getDownloadDeletedCampaigns() {
    return downloadDeletedCampaigns;
  }

  /**
   * Defines if StarterKit should download campaigns deleted in AdWords. If
   * true, StarterKit will download Ad Groups, Text Ads, Creatives, etc. from
   * deleted Campaigns. This may affect performance drastically
   * 
   * @param status if true, downloads the deleted campaigns
   */
  public void setDownloadDeletedCampaigns(boolean status) {
    downloadDeletedCampaigns = status;
  }

  public boolean hasKey(JSONObject jsonObject, String key, String msg) {
    if (jsonObject.has(key)) {
      return true;
    } else {
      log.debug(msg);
    }
    return false;
  }
  
  public String getDbName() {
    return dbName;
  }
  
  public void setDbName(String name) {
    dbName = name;
  }
}
